package org.quickjsf.common.util;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import javax.persistence.Id;

import org.apache.commons.lang3.StringUtils;

public class DomainUtil {
	
	public static String getFieldId(Class<?> cls) {
		Method[] declaredFields = cls.getDeclaredMethods();
		for (Method method : declaredFields) {
			if (method.getName().startsWith("get") && null != method.getAnnotation(Id.class)) {
				return method.getName().substring(3,4).toLowerCase() + method.getName().substring(4);
			}
		} 
		return null;
	}
	
	public static Class<?> getIdReturnType(Class<?> cls) {
		Method[] declaredFields = cls.getDeclaredMethods();
		for (Method method : declaredFields) {
			if (method.getName().startsWith("get") && null != method.getAnnotation(Id.class)) {
				return method.getReturnType();
			}
		} 
		return null;
	}
	
	public static String getIdHsql(Class<?> cls, Serializable id) {
		
		Method[] declaredFields = cls.getDeclaredMethods();
		
		for (Method method : declaredFields) {
			
			if (method.getName().startsWith("get") && null != method.getAnnotation(Id.class)) {
				
				StringBuffer hsql = new StringBuffer();
				hsql.append(method.getName().substring(3,4).toLowerCase() + method.getName().substring(4));
				hsql.append("=");
				try {
					hsql.append(method.getReturnType().getConstructor(String.class).newInstance(id));
				} catch (Exception e) {
					e.printStackTrace();
					return null;
				}
				return hsql.toString();
			}
		} 
		
		return null;
	}
	
	public static Object getFieldIdValue(Object obj) throws Exception {
		Class<?> cls = obj.getClass();
		Method[] declaredFields = cls.getDeclaredMethods();
		for (Method method : declaredFields) {
			if (method.getName().startsWith("get") && null != method.getAnnotation(Id.class)) {
				return method.invoke(obj, new Object[]{});
			}
		} 
		return null;
	}
	
	public static Object setFieldValue(Object obj, String field, Object value) {
		try {
			String methodName = "set"+StringUtils.capitalize(field);
			return invoke(obj, methodName, new Class[]{getFieldReturnType(obj.getClass(), field)}, value);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public static Object getFieldValue(Object obj, String field) {
		try {
			return invoke(obj, "get"+StringUtils.capitalize(field), new Class[]{});
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public static Class<?> getFieldReturnType(Class<?> cls, String field) {
		try {
			return cls.getMethod("get"+StringUtils.capitalize(field), new Class[]{}).getReturnType();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public static Object copyProperty(Object bean, String name, Object value) throws Exception {
		Method getter = value.getClass().getMethod("get"+StringUtils.capitalize(name), new Class[]{});
		Object val = getter.invoke(value);
		return bean.getClass().getMethod("set"+StringUtils.capitalize(name), getter.getReturnType()).invoke(bean, val);
	}
	
	public static Object invoke(Object bean, String name, Class<?>[] parameterTypes, Object... args) throws Exception {
		return bean.getClass().getMethod(name, parameterTypes).invoke(bean, args);
	}
	
	/**
	 * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
	 *
	 * @param packageName The base package
	 * @return The classes
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public static Class<?>[] getPackageClasses(String packageName)
	        throws ClassNotFoundException, IOException {
	    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	    assert classLoader != null;
	    String path = packageName.replace('.', '/');
	    Enumeration<URL> resources = classLoader.getResources(path);
	    List<File> dirs = new ArrayList<File>();
	    while (resources.hasMoreElements()) {
	        URL resource = resources.nextElement();
	        dirs.add(new File(resource.getFile()));
	    }
	    ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
	    for (File directory : dirs) {
	        classes.addAll(findClasses(directory, packageName));
	    }
	    return classes.toArray(new Class[classes.size()]);
	}

	/**
	 * Recursive method used to find all classes in a given directory and subdirs.
	 *
	 * @param directory   The base directory
	 * @param packageName The package name for classes found inside the base directory
	 * @return The classes
	 * @throws ClassNotFoundException
	 */
	public static List<Class<?>> findClasses(File directory, String packageName) throws ClassNotFoundException {
	    List<Class<?>> classes = new ArrayList<Class<?>>();
	    if (!directory.exists()) {
	        return classes;
	    }
	    File[] files = directory.listFiles();
	    for (File file : files) {
	        if (file.isDirectory()) {
	            assert !file.getName().contains(".");
	            classes.addAll(findClasses(file, packageName + "." + file.getName()));
	        } else if (file.getName().endsWith(".class")) {
	            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
	        }
	    }
	    return classes;
	}
}
